在規劃 life cycle的章節,筆者提到過可以在物件執行階段加上 run_callbacks
來在您想要的地方運作指定的 callbacks,但示例的前前後後好像都沒有寫到 validation 的階段耶?他跑到哪裡去了?
還記不記得筆者在這篇提過 ActiveModel::Validations::Callbacks
這個 module ?
在使用它之後,就可以加上像是
before_validation after_validation
的 callbacks 囉!
這些 callbacks 加上 Rails 內建的驗證規則,能夠讓你的 form object 很簡單就能做到防止使用者操作失誤的防呆功用,以及權限的管理等等,而這也是驗證機制的主要功能之一 - 防止意外操作。
然而,一旦要做到權限管理,我想有的人可能會這樣做:
class ArticleForm
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Validations::Callbacks
attr_accessor :current_user
# current_user 在 controller 階段帶入,為了不被 attribute_types 自動抓出來 render 欄位,改用 attr_accessor
attribute :title, :string, default: ''
attribute :content, :string, default: ''
before_validation :check_permission
validates_length_of :title, minimun: 5
validates_length_of :content, minimun: 20
# ... 一些 validation 與其他定義
def check_permission
# .. 對傳進來的 current_user 做權限檢查
if current_user.role < :admin # 像這種寫法需要搭配 comparable 來使用,之後會再提到
raise "insufficient permission." # 如果權限不夠就拋出錯誤
end
end
end
當然,這樣的模式在正式環境下運作是沒什麼問題的,但要是有額外的客制需求,需要進 Rails console 直接拿這個 form object 來操作,請問要去哪裡生出一個 current_user
?如果有建立 User model 還好說,從 DB query 出來放進去就是了,那萬一是存在 session 的資料呢?
或者,如果你的 form object 還規劃了像是 after_execute :send_notification
這種動作後寄出通知信給使用者的動作,但我在 Rails console 裡面跑 script 改資料不想寄出通知信呀!那是不是就只能暫時的 monket patch 把 send_notification
給覆蓋掉?無形中增加維運的成本。
在規劃不論是 model 或是 service object 的時候永遠要記得考慮這點,那就是要維持這東西就算在 Rails console 也能很好用的程度。
你做的東西,除了用來服務使用者之外,同時也要能夠兼顧到使用彈性。
儘管筆者介紹的這一系列不管是 callbacks, validation 等等都很好用,但也不能就一股腦的把邏輯通通組合在一起。
像上面這個範例,筆者就會把權限判斷拆到 controller 層去做判斷:
class ArticleController
before_action :check_permission, only: :create # 在 controller 層去判斷權限
after_action :send_notification, only: :create # 寄送通知也放在 controller 層
def create
@form = ArticleForm.new(article_params)
if @form.valid?
@form.execute
redirect_to article_path @form.article
else
render :new
end
end
end
這樣一來,這個 form object 就只需要負責對傳送進去的參數做驗證,也不會做多餘的事情,皆大歡喜!